package com.qf.service.imp;

import com.qf.entity.Hotal;
import com.qf.entity.SearchParams;
import com.qf.service.ISearchHotalService;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.sort.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.ScriptField;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class SearchHotalServiceImpl implements ISearchHotalService {
    @Autowired
    private ElasticsearchRestTemplate restTemplate;

    private String script1 = "doc ['rooms.prices.number'].value == doc ['rooms.prices.hasNumber'].value";

    private String avgScript = "double avgPrice = Integer.MAX_VALUE;\n" +
            "          boolean flag = true;\n" +
            "          for(room in params._source.rooms){\t\n" +
            "          \tdouble roomAvgPrice = 0;\n" +
            "          \tint days = 0;\n" +
            "          \tfor(priceInfo in room.prices){\n" +
            "          \t\tif(priceInfo.date >= params.beginTime && priceInfo.date < params.endTime){\n" +
            "          \t\t\tif(priceInfo.number == priceInfo.hasNumber){\n" +
            "          \t\t\t\tflag = false;\n" +
            "          \t\t\t}\n" +
            "          \t\t\troomAvgPrice += priceInfo.price;\n" +
            "          \t\t\tdays++;\n" +
            "          \t\t}\n" +
            "          \t}\n" +
            "          \troomAvgPrice = roomAvgPrice / days;\n" +
            "          \tif(roomAvgPrice >= params.minPrice && roomAvgPrice <= params.maxPrice && flag){\n" +
            "          \t\tavgPrice = Math.min(avgPrice, roomAvgPrice);\n" +
            "          \t}\n" +
            "          \tflag = true;\n" +
            "          }\n" +
            "          return avgPrice == Integer.MAX_VALUE ? -1 : avgPrice;";

    private String disScript = "doc.location.planeDistance(params.lat,params.lon) / 1000";

    private List<Hotal> search(QueryBuilder queryBuilder, List<SortBuilder> sortBuilders, ScriptField... scriptFields){
        //构建原生的搜索对象
        NativeSearchQuery query = new NativeSearchQuery(queryBuilder,null,sortBuilders);

        if (scriptFields != null){
            query.addScriptField(scriptFields);
            //设置其他显示的字段
            String[] exclude = {"rooms"};
            query.addSourceFilter(new FetchSourceFilter(null,exclude));
        }

        SearchHits<Hotal> searchHits = restTemplate.search(query, Hotal.class);

        List<Hotal> hotals = new ArrayList<>();

        NumberFormat numberFormat = new DecimalFormat("0.00");

        searchHits.stream().forEach(hotalSearchHit -> {
            Hotal hotal = hotalSearchHit.getContent();
            if (hotal.getAvgPrice() != -1){
                hotal.setDistance(Double.parseDouble(numberFormat.format(hotal.getDistance())));

                hotal.setAvgPrice(Double.parseDouble(numberFormat.format(hotal.getAvgPrice())));

                hotals.add(hotal);
            }
        });

        return hotals;
    }

    @Override
    public List<Hotal> searchHotal(SearchParams searchParams) {
        BoolQueryBuilder primaryQuery = QueryBuilders.boolQuery();
        //先写最外层查询，依次写里层查询
        if (searchParams.getBeginTime() != null && searchParams.getEndTime() != null){
            BoolQueryBuilder priceBool = QueryBuilders.boolQuery()
                    .must(QueryBuilders
                            .rangeQuery("rooms.prices.date")
                            .gte(searchParams.getBeginTime().getTime())
                            .lt(searchParams.getEndTime().getTime()))
                    .must(QueryBuilders.scriptQuery(new Script(script1)));

            NestedQueryBuilder roomNestQuery = QueryBuilders.nestedQuery("rooms.prices", priceBool, ScoreMode.Avg);

            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().mustNot(roomNestQuery);

            NestedQueryBuilder roomsQuery = QueryBuilders.nestedQuery("rooms", boolQuery, ScoreMode.Avg);

            primaryQuery.must(roomsQuery);
        }

        if (!StringUtils.isEmpty(searchParams.getKeyword())) {
            primaryQuery
                    .should(QueryBuilders
                            .multiMatchQuery(searchParams.getKeyword())
                            .field("hotalName", 3)
                            .field("hotalName.pinyin")
                            .field("hotalInfo")
                            .field("hotalInfo.pinyin")
                            .field("keyword", 2)
                            .field("keyword.pinyin")
                            .field("brand", 2)
                            .field("brand.pinyin")
                            .field("address")
                            .field("address.pinyin")
                            .field("reg")
                            .field("reg.pinyin"))
                    .should(QueryBuilders
                            .nestedQuery("rooms",
                                    QueryBuilders
                                            .multiMatchQuery(searchParams.getKeyword())
                                            .field("rooms.title")
                                            .field("rooms.title.pinyin"), ScoreMode.Avg))
                    .minimumShouldMatch(1);
        }

        NestedQueryBuilder cityQuery = QueryBuilders.nestedQuery("city",
                QueryBuilders.termQuery("city.cityName.keyword",
                        searchParams.getCityName()), ScoreMode.Avg);

        BoostingQueryBuilder executeQuery = QueryBuilders.boostingQuery(primaryQuery, cityQuery).negativeBoost(5);

        Map<String,Object> params = new HashMap<>();

        params.put("beginTime",
                searchParams.getBeginTime() != null ? searchParams.getBeginTime().getTime() : Long.MIN_VALUE);
        params.put("endTime",
                searchParams.getEndTime() != null ? searchParams.getEndTime().getTime() : Long.MAX_VALUE);
        params.put("minPrice",
                searchParams.getMinPrice() != null ? searchParams.getMinPrice() : 0);
        params.put("maxPrice",
                searchParams.getMaxPrice() != null ? searchParams.getMaxPrice() : 10000);
        params.put("lat",
                searchParams.getLat());
        params.put("lon",
                searchParams.getLon());

        ScriptField scriptField = new ScriptField("avgPrice",
                new Script(ScriptType.INLINE,"painless",avgScript,params));

        ScriptField scriptField2 = new ScriptField("distance",
                new Script(ScriptType.INLINE,"painless",disScript,params));

        List<SortBuilder> sortBuilders = new ArrayList<>();

        switch (searchParams.getSortType()){
            case 1:
                //智能排序
                ScoreSortBuilder scoreSortBuilder = SortBuilders.scoreSort();
                sortBuilders.add(scoreSortBuilder);
                break;
            case 2:
                //价格排序
                ScriptSortBuilder priceSortBuilder = SortBuilders.scriptSort(
                        new Script(ScriptType.INLINE, "painless", avgScript, params),
                        ScriptSortBuilder.ScriptSortType.NUMBER);
                sortBuilders.add(priceSortBuilder);
                break;
            case 3:
                //离我最近
                GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location",
                        searchParams.getLat(), searchParams.getLon()).order(SortOrder.ASC);
                sortBuilders.add(sortBuilder);
                break;
        }

        return search(executeQuery,sortBuilders,scriptField,scriptField2);
    }
}
